/*
 * Copyright (c) 2007 TripAdvisor (http://www.tripadvisor.com) @author
 * fitzgerald@tripadvisor.com
 */
package com.tripadvisor.friendster;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class FriendsterClient
{
    private static final Logger log          = Logger.getLogger(FriendsterClient.class);

    static final String         API_SERVER   = "http://api.friendster.com";
    static final String         API_VERSION  = "v1";

    private String              publicKey;
    private String              secretKey;
    private String              sessionKey;
	private static long 		last_nonce = 1;
    private int                 conn_timeout = 1000;
    private int                 read_timeout = 10000;

    /**
     * Create a new FriendsterClient to communicate with the Friendster API
     * 
     * @param sessionKey
     *            the session key for this user
     * @param publicKey
     *            the public key for the application
     * @param secretKey
     *            the secret key for the application
     */
    public FriendsterClient(String sessionKey, String publicKey, String secretKey)
    {
        this.sessionKey = sessionKey;
        this.publicKey = publicKey;
        this.secretKey = secretKey;
    }

	private User convertToUser(Document doc) throws XPathExpressionException {
		XPath xpath = XPathFactory.newInstance().newXPath();

		String last_name = xpath.evaluate("/user_response/user/last_name", doc);
		String first_name = xpath.evaluate("/user_response/user/first_name",
				doc);
		String primary_photo = xpath.evaluate(
				"/user_response/user/primary_photo_url", doc);
		
		//Add more properties here if necessary
		User user = new User();
		user.setFirstName(first_name);
		user.setLastName(last_name);
		user.setPrimaryPhoto(primary_photo);
		
		return user;
	}

	//Gets the current user
	public User getUser() throws FriendsterException, Exception {
		Document doc = sendGetRequest(FriendsterMethod.USER);
		return convertToUser(doc);
	}
	
    public Document getUser(int uid) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.USER, String.valueOf(uid));
    }

    public Document getUsers(List<Integer> uids) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.USER, FriendsterUtil.collectionToCSV(uids));
    }

    public List<Integer> getFriends(int uid) throws FriendsterException, Exception
    {
        Document doc = sendGetRequest(FriendsterMethod.FRIENDS, String.valueOf(uid));
        XPath xpath = XPathFactory.newInstance().newXPath();
        NodeList nl = (NodeList) xpath.evaluate("/friends_response/friends/user", doc, XPathConstants.NODESET);
        int sz = (nl != null) ? nl.getLength() : 0;
        List<Integer> friends = new ArrayList<Integer>();
        for (int i = 0; i < sz; i++)
        {
            friends.add(Integer.parseInt(nl.item(i).getTextContent()));
        }

        return friends;
    }

    public int getDepth(int uid1, int uid2) throws FriendsterException, Exception
    {
        String args = uid1 + "," + uid2;
        Document doc = sendGetRequest(FriendsterMethod.DEPTH, args);
        XPath xpath = XPathFactory.newInstance().newXPath();
        String s = xpath.evaluate("/depth_response/friend_info/depth", doc);

        int n = -1;
        if (s != null)
        {
            n = Integer.parseInt(s);
        }
        return n;
    }

	public String getSession(String token) throws FriendsterException,
			Exception {
		Map<String, String> params = new HashMap<String, String>();
		params.put("auth_token", token);
		Document doc = sendPostRequest(FriendsterMethod.SESSION, null, params);
		XPath xpath = XPathFactory.newInstance().newXPath();
		return xpath.evaluate("/session_response/session_key", doc);
	}

	public String getToken() throws FriendsterException, Exception {
		Document doc = sendPostRequest(FriendsterMethod.TOKEN, null, null);
		XPath xpath = XPathFactory.newInstance().newXPath();
		return xpath.evaluate("/token_response/auth_token", doc);
	}

	public List<Integer> getFriends() throws FriendsterException, Exception {
		Document doc = sendGetRequest(FriendsterMethod.FRIENDS);
		XPath xpath = XPathFactory.newInstance().newXPath();
		NodeList nl = (NodeList) xpath.evaluate(
				"/friends_response/friends/uid", doc, XPathConstants.NODESET);
		int sz = (nl != null) ? nl.getLength() : 0;
		List<Integer> friends = new ArrayList<Integer>();
		for (int i = 0; i < sz; i++) {
			friends.add(Integer.parseInt(nl.item(i).getTextContent()));
		}

		return friends;
	}
	
	
    public Document getPhotos() throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.PHOTOS);
    }

    public Document getPhotos(int uid) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.PHOTOS, String.valueOf(uid));
    }

    public Document getPhoto(long pid) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.PHOTO, String.valueOf(pid));
    }

    public Document getPhoto(int uid, long pid) throws FriendsterException, Exception
    {
        List<String> resources = new ArrayList<String>(2);
        resources.add(String.valueOf(uid));
        resources.add(String.valueOf(pid));
        return sendGetRequest(FriendsterMethod.PHOTO, resources);
    }

    public Document getPrimaryPhoto() throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.PRIMARYPHOTO);
    }

    public Document getPrimaryPhoto(int uid) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.PRIMARYPHOTO, String.valueOf(uid));
    }

    public Document getShoutout() throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.SHOUTOUT);
    }
    
    public Document getShoutout(int uid) throws FriendsterException, Exception
    {
        return sendGetRequest(FriendsterMethod.SHOUTOUT, String.valueOf(uid));
    }

    public Document getShoutout(List<Integer> uids) throws FriendsterException, Exception
    {
        if (uids == null)
        {
            return null;
        }

        return sendGetRequest(FriendsterMethod.SHOUTOUT, FriendsterUtil.collectionToCSV(uids));
    }
    
    public Document setShoutout(String content) throws FriendsterException, Exception
    {
        Map<String, String> params = new HashMap<String, String>();
        params.put("content", content);

        return sendPostRequest(FriendsterMethod.SHOUTOUT, null, params);
    }

    public int updateProfile(String content, int instanceId) throws FriendsterException, Exception
    {
        Map<String, String> params = new HashMap<String, String>();
        params.put("content", content);
        if (instanceId > 0)
        {
            params.put("instance_id", String.valueOf(instanceId));
        }
        Document doc = sendPostRequest(FriendsterMethod.WIDGET, null, params);

        int nInstanceId = -1;
        NodeList nl = doc.getElementsByTagName("instance_id");
        if (nl != null)
        {
            nInstanceId = Integer.valueOf(nl.item(0).getTextContent());
        }

        return nInstanceId;
    }
    
    private Document sendGetRequest(FriendsterMethod method) throws FriendsterException, Exception
    {
        return sendRequest(method, null, null, false);
    }

    private Document sendGetRequest(FriendsterMethod method, String resource) throws FriendsterException, Exception
    {
        return sendRequest(method, Collections.singletonList(resource), null, false);
    }

    private Document sendGetRequest(FriendsterMethod method, List<String> resources) throws FriendsterException,
            Exception
    {
        return sendRequest(method, resources, null, false);
    }

    private Document sendPostRequest(FriendsterMethod method, List<String> resources, Map<String, String> params)
            throws FriendsterException, Exception
    {
        return sendRequest(method, resources, params, true);
    }

    public Document sendRequest(FriendsterMethod method, List<String> resources, Map<String, String> params,
            boolean bPost) throws FriendsterException, Exception
    {
        StringBuilder url = new StringBuilder();
        url.append(API_SERVER);
        url.append("/");
        url.append(API_VERSION);
        url.append("/");
        url.append(method.toString().toLowerCase());
        if (resources != null)
        {
            for (String resource : resources)
            {
                url.append("/");
                url.append(resource);
            }
        }

        if (params == null)
        {
            params = new HashMap<String, String>();
        }

        params.put("api_key", publicKey);
        params.put("session_key", sessionKey);
        
		// Try to prevent nonce collisions
		long nonce = System.currentTimeMillis();
		if (nonce <= last_nonce) {
			nonce = last_nonce + 1;
		}
		params.put("nonce", Long.toString(nonce));
		last_nonce = nonce;
		
		
        String queryString = FriendsterUtil.convertToUrlString(params);
        String sig = FriendsterUtil.generateSig(url.toString(), queryString, secretKey);
        queryString += "&sig=" + sig;

        if (!bPost)
        {
            url.append("?");
            url.append(queryString);
        }

        URL finalUrl = new URL(url.toString());

        log.debug("request url: " + finalUrl.toString());

        HttpURLConnection conn = (HttpURLConnection) finalUrl.openConnection();
        conn.setConnectTimeout(conn_timeout);
        conn.setReadTimeout(read_timeout);

        if (bPost)
        {
            conn.setRequestMethod("POST");
        }
        conn.setDoOutput(true);
        conn.connect();

        if (bPost)
        {
            log.debug("post data: " + queryString);
            conn.getOutputStream().write(queryString.getBytes());
        }

        boolean error = conn.getResponseCode() != HttpURLConnection.HTTP_OK;

        InputStream is = error ? conn.getErrorStream() : conn.getInputStream();
        if (log.isDebugEnabled())
        {
            is = FriendsterUtil.logInputStream(is, log, "response");
        }

        Document doc = FriendsterUtil.getDocument(is);

        if (error)
        {
            Map m = new HashMap<String, String>();
            int errorCode = Integer.valueOf(FriendsterUtil.getNodeText("error_code", doc));
            String errorMsg = FriendsterUtil.getNodeText("error_message", doc);

            throw new FriendsterException(errorCode, errorMsg);
        }

        return doc;
    }

    public void setConnectionTimeout(int millis)
    {
        conn_timeout = millis;
    }

    public void setReadTimeout(int millis)
    {
        read_timeout = millis;
    }
}
